package org.durandj.ystream.client.util;

import java.security.InvalidParameterException;

import org.durandj.ystream.client.message.Message;
import org.durandj.ystream.client.message.MessageHandler;
import org.durandj.ystream.client.message.MessageID;
import org.durandj.ystream.client.message.Message.Priority;

/**
 * A utility class that provides timer functionality.
 * 
 * @author durandj
 * @version 1.0
 *
 */
public class Timer
{
	/**
	 * The time for the timer in milliseconds.
	 */
	private long delay;
	
	/**
	 * The projected end time of the timer. This is used for handling pausing of the timer.
	 */
	private long endTime;
	
	/**
	 * Flag that tells the timer it has been paused.
	 */
	private boolean paused;
	
	/**
	 * The class to send a message to when the timer finishes.
	 */
	private MessageHandler handler;
	
	/**
	 * The priority of the message for completion.
	 */
	private Priority priority;
	
	/**
	 * The timer thread.
	 */
	private Thread thread;
	
	/**
	 * Creates a timer that halts execution for the desired time (given in milliseconds).
	 * 
	 * @param delay - time in milliseconds
	 */
	@SuppressWarnings("static-access")
	public Timer(long delay)
	{
		if(delay <= 0)
			throw new InvalidParameterException("Must give positive time value.");
		
		this.endTime = -1;
		this.handler = null;
		this.priority = null;
		
		try
		{
			Thread.currentThread().sleep(delay);
		}
		catch(InterruptedException e)
		{}
	}
	
	/**
	 * Creates a timer that will notify the given class when it completes. The time
	 * is given in milliseconds and the priority must be specified for the importance
	 * of the timer.
	 * 
	 * @param delay - the time in milliseconds
	 * @param handler - the handler of the message
	 * @param priority - the priority of the completion message
	 */
	public Timer(long delay, MessageHandler handler, Priority priority)
	{
		if(delay <= 0)
			throw new InvalidParameterException("Must give positive time value.");
		
		if(handler == null)
			throw new NullPointerException("Must give a message handler.");
		
		if(priority == null)
			throw new NullPointerException("Must give the priority of the timer.");
		
		this.delay = delay;
		this.endTime = -1;
		this.handler = handler;
		this.priority = priority;
	}
	
	/**
	 * Starts the timer. This will also restart the timer if it has been paused.
	 */
	public void start()
	{
		if(thread != null && thread.isAlive())
			throw new IllegalStateException("Timer is already running.");
		
		if(delay == -1)
			throw new IllegalStateException("Timer has not been reset.");
		
		endTime = System.currentTimeMillis() + delay;
		paused = false;
		thread = new Thread(new Runnable() {
			@SuppressWarnings("static-access")
			@Override
			public void run()
			{
				boolean interrupted = false;
				
				try
				{
					thread.sleep(Timer.this.delay);
				}
				catch(InterruptedException e)
				{
					interrupted = true;
				}
				
				if(paused)
				{
					Timer.this.delay = endTime - System.currentTimeMillis();
					return;
				}
				
				if(!interrupted)
				{
					Timer.this.delay = -1;
					endTime = -1;
					Timer.this.handler.queueMessage(new Message(Timer.this.priority, MessageID.TIMER_EXPIRED, this));
				}
			}
		}, "Timer - " + handler.getClass().getSimpleName());
		thread.start();
	}
	
	/**
	 * Checks if the timer is running.
	 * 
	 * @return is the timer running?
	 */
	public boolean isRunning()
	{
		return thread.isAlive();
	}
	
	/**
	 * Pauses the timer after it has been started. The timer can be restarted by calling the <code>start</code> method
	 * at which point the timer will resume at the correct time.
	 */
	public void pause()
	{
		if(thread != null && !thread.isAlive())
			throw new IllegalStateException("Timer is not running.");
		
		paused = true;
		stop();
	}
	
	/**
	 * Stops the timer. The timer must be reset to run again.
	 */
	public void stop()
	{
		if(thread != null && !thread.isAlive())
			throw new IllegalStateException("Timer is not running.");
		
		thread.interrupt();
	}
	
	/**
	 * Resets the timer after it has finished or been stopped.
	 * 
	 * @param delay - the new time in milliseconds
	 */
	public void reset(long delay)
	{
		if(thread != null && thread.isAlive())
			throw new IllegalStateException("Timer is running.");
		
		if(delay <= 0)
			throw new InvalidParameterException("Must give a positive time value.");
		
		this.delay = delay;
	}
	
	/**
	 * Sets the handler and priority of the timer.
	 * 
	 * @param handler - the new handler
	 * @param priority - the new priority of the timer
	 */
	public void setHandler(MessageHandler handler, Priority priority)
	{
		if(thread != null && thread.isAlive())
			throw new IllegalStateException("Cannot change this when timer is running.");
		
		if(handler == null)
			throw new NullPointerException("Must give a valid handler.");
		
		if(priority == null)
			throw new NullPointerException("Must give a valid priority value.");
		
		this.handler = handler;
		this.priority = priority;
	}
}
